home *** CD-ROM | disk | FTP | other *** search
/ Java Interactive Reference Guide / Java Interactive Reference Guide.iso / autorun / source.dir / 00012_15.txt < prev    next >
Encoding:
Text File  |  1980-01-11  |  24.8 KB  |  933 lines

  1. /*
  2.  * @(#)Animator.java    1.5 95/11/29 Herb Jellinek
  3.  *
  4.  * Copyright (c) 1994-1995 Sun Microsystems, Inc. All Rights Reserved.
  5.  *
  6.  * Permission to use, copy, modify, and distribute this software
  7.  * and its documentation for NON-COMMERCIAL or COMMERCIAL purposes and
  8.  * without fee is hereby granted.
  9.  * Please refer to the file http://java.sun.com/copy_trademarks.html
  10.  * for further important copyright and trademark information and to
  11.  * http://java.sun.com/licensing.html for further important licensing
  12.  * information for the Java (tm) Technology.
  13.  *
  14.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
  15.  * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
  16.  * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  17.  * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
  18.  * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
  19.  * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
  20.  *
  21.  * THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE
  22.  * CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE
  23.  * PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT
  24.  * NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE
  25.  * SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
  26.  * SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE
  27.  * PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES").  SUN
  28.  * SPECIFICALLY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR
  29.  * HIGH RISK ACTIVITIES.
  30.  */
  31.  
  32. import java.io.InputStream;
  33. import java.awt.*;
  34. import java.awt.image.ImageProducer;
  35. import java.applet.Applet;
  36. import java.applet.AudioClip;
  37. import java.util.Vector;
  38. import java.util.Hashtable;
  39. import java.util.Enumeration;
  40. import java.net.URL;
  41. import java.net.MalformedURLException;
  42.  
  43. /**
  44.  * An applet that plays a sequence of images, as a loop or a one-shot.
  45.  * Can have a soundtrack and/or sound effects tied to individual frames.
  46.  *
  47.  * @author Herb Jellinek
  48.  * @version 1.5, 29 Nov 1995
  49.  */
  50.  
  51. public class Animator extends Applet implements Runnable {
  52.     
  53.     /**
  54.      * The images, in display order (Images).
  55.      */
  56.     Vector images = null;
  57.  
  58.     /**
  59.      * Duration of each image (Integers, in milliseconds).
  60.      */
  61.     Hashtable durations = null;
  62.  
  63.     /**
  64.      * Sound effects for each image (AudioClips).
  65.      */
  66.     Hashtable sounds = null;
  67.  
  68.     /**
  69.      * Position of each image (Points).
  70.      */
  71.     Hashtable positions = null;
  72.  
  73.     /**
  74.      * MediaTracker 'class' ID numbers.
  75.      */
  76.  
  77.     static final int STARTUP_ID    = 0;
  78.     static final int BACKGROUND_ID = 1;
  79.     static final int ANIMATION_ID  = 2;
  80.  
  81.     /**
  82.      * Start-up image URL, if any.
  83.      */
  84.     URL startUpImageURL = null;
  85.  
  86.     /**
  87.      * Start-up image, if any.
  88.      */
  89.     Image startUpImage = null;
  90.  
  91.     /**
  92.      * Background image URL, if any.
  93.      */
  94.     URL backgroundImageURL = null;
  95.  
  96.     /**
  97.      * Background image, if any.
  98.      */
  99.     Image backgroundImage = null;
  100.  
  101.     /**
  102.      * The soundtrack's URL.
  103.      */
  104.     URL soundtrackURL = null;
  105.  
  106.     /**
  107.      * The soundtrack.
  108.      */
  109.     AudioClip soundtrack;
  110.  
  111.     /**
  112.      * Largest width.
  113.      */
  114.     int maxWidth = 0;
  115.  
  116.     /**
  117.      * Largest height.
  118.      */
  119.     int maxHeight = 0;
  120.  
  121.     /**
  122.      * Was there a problem loading the current image?
  123.      */
  124.     boolean imageLoadError = false;
  125.  
  126.     /**
  127.      * The directory or URL from which the images are loaded
  128.      */
  129.     URL imageSource = null;
  130.  
  131.     /**
  132.      * The directory or URL from which the sounds are loaded
  133.      */
  134.     URL soundSource = null;
  135.  
  136.     /**
  137.      * The thread animating the images.
  138.      */
  139.     Thread engine = null;
  140.  
  141.     /**
  142.      * The current loop slot - index into 'images.'
  143.      */
  144.     int frameNum;
  145.  
  146.     /**
  147.      * frameNum as an Object - suitable for use as a Hashtable key.
  148.      */
  149.     Integer frameNumKey;
  150.     
  151.     /**
  152.      * The current X position (for painting).
  153.      */
  154.     int xPos = 0;
  155.     
  156.     /**
  157.      * The current Y position (for painting).
  158.      */
  159.     int yPos = 0;
  160.     
  161.     /**
  162.      * The default number of milliseconds to wait between frames.
  163.      */
  164.     public static final int defaultPause = 3900;
  165.     
  166.     /**
  167.      * The global delay between images, which can be overridden by
  168.      * the PAUSE parameter.
  169.      */
  170.     int globalPause = defaultPause;
  171.  
  172.     /**
  173.      * Whether or not the thread has been paused by the user.
  174.      */
  175.     boolean userPause = false;
  176.  
  177.     /**
  178.      * Repeat the animation?  If false, just play it once.
  179.      */
  180.     boolean repeat;
  181.  
  182.     /**
  183.      * The offscreen image, used in double buffering
  184.      */
  185.     Image offScrImage;
  186.  
  187.     /**
  188.      * The offscreen graphics context, used in double buffering
  189.      */
  190.     Graphics offScrGC;
  191.  
  192.     /**
  193.      * The MediaTracker we use to load our images.
  194.      */
  195.     MediaTracker tracker;
  196.     
  197.     /**
  198.      * Can we paint yet?
  199.      */
  200.     boolean loaded = false;
  201.  
  202.     /**
  203.      * Was there an initialization error?
  204.      */
  205.     boolean error = false;
  206.  
  207.     /**
  208.      * What we call an image file in messages.
  209.      */
  210.     final static String imageLabel = "image";
  211.     
  212.     /**
  213.      * What we call a sound file in messages.
  214.      */
  215.     final static String soundLabel = "sound";
  216.     
  217.     /**
  218.      * Print silly debugging info?
  219.      */
  220.     final boolean debug = false;
  221.  
  222.     /**
  223.      * Applet info.
  224.      */
  225.     public String getAppletInfo() {
  226.     return "Animator v1.5, by Herb Jellinek";
  227.     }
  228.  
  229.     /**
  230.      * Parameter info.
  231.      */
  232.     public String[][] getParameterInfo() {
  233.     String[][] info = {
  234.         {"imagesource",     "URL",         "a directory"},
  235.         {"startup",     "URL",         "displayed at startup"},
  236.         {"background",     "URL",         "displayed as background"},
  237.         {"startimage",     "int",         "start index"},
  238.         {"endimage",     "int",         "end index"},
  239.         {"namepattern",     "URL",          "used to generate indexed names"},
  240.         {"pause",             "int",         "milliseconds"},
  241.         {"pauses",             "ints",     "milliseconds"},
  242.         {"repeat",             "boolean",     "repeat or not"},
  243.         {"positions",    "coordinates",     "path"},
  244.         {"soundsource",    "URL",         "audio directory"},
  245.         {"soundtrack",    "URL",         "background music"},
  246.         {"sounds",        "URLs",        "audio samples"},
  247.     };
  248.     return info;
  249.     }
  250.  
  251.     /**
  252.      * Print silly debugging info.
  253.      */
  254.     void dbg(String s) {
  255.     if (debug) {
  256.         System.out.println("> "+s);
  257.     }
  258.     }
  259.  
  260.     /**
  261.      * Local version of getParameter for debugging purposes.
  262.      */
  263.     public String getParameter(String key) {
  264.     String result = super.getParameter(key);
  265.     dbg("getParameter("+key+") = "+result);
  266.     return result;
  267.     }
  268.  
  269.     final int setFrameNum(int newFrameNum) {
  270.     frameNumKey = new Integer(frameNum = newFrameNum);
  271.     return frameNum;
  272.     }
  273.     
  274.     void updateMaxDims(Dimension dim) {
  275.     maxWidth = Math.max(dim.width, maxWidth);
  276.     maxHeight = Math.max(dim.height, maxHeight);
  277.     dbg("New width = "+maxWidth+", height = "+maxHeight);
  278.     }
  279.  
  280.     /**
  281.      * Parse the IMAGES parameter.  It looks like
  282.      * 1|2|3|4|5, etc., where each number (item) names a source image.
  283.      *
  284.      * @return a Vector of (URL) image file names.
  285.      */
  286.     Vector parseImages(String attr)
  287.     throws MalformedURLException {
  288.     Vector result = new Vector(10);
  289.     for (int i = 0; i < attr.length(); ) {
  290.         int next = attr.indexOf('|', i);
  291.         if (next == -1) next = attr.length();
  292.         String file = attr.substring(i, next);
  293.         result.addElement(new URL(imageSource, "T"+file+".gif"));
  294.         i = next + 1;
  295.     }
  296.     return result;
  297.     }
  298.  
  299.     /**
  300.      * Fetch the images named in the argument, updating 
  301.      * maxWidth and maxHeight as we go.
  302.      * Is restartable.
  303.      *
  304.      * @param images a Vector of URLs
  305.      * @return true if all went well, false otherwise.
  306.      */
  307.     boolean fetchImages(Vector images) {
  308.     int i;
  309.     int size = images.size();
  310.     for (i = 0; i < size; i++) {
  311.         Object o = images.elementAt(i);
  312.         if (o instanceof URL) {
  313.         URL url = (URL)o;
  314.         tellLoadingMsg(url, imageLabel);
  315.         Image im = getImage(url);
  316.         tracker.addImage(im, ANIMATION_ID);
  317.         images.setElementAt(im, i);
  318.         }
  319.     }
  320.  
  321.     try {
  322.         tracker.waitForID(ANIMATION_ID);
  323.     } catch (InterruptedException e) {}
  324.     if (tracker.isErrorID(ANIMATION_ID)) {
  325.         return false;
  326.     }
  327.     
  328.     for (i = 0; i < size; i++) {
  329.         updateMaxDims(getImageDimensions((Image)images.elementAt(i)));
  330.     }
  331.  
  332.     return true;
  333.     }
  334.  
  335.     /**
  336.      * Parse the SOUNDS parameter.  It looks like
  337.      * train.au||hello.au||stop.au, etc., where each item refers to a
  338.      * source image.  Empty items mean that the corresponding image
  339.      * has no associated sound.
  340.      *
  341.      * @return a Hashtable of SoundClips keyed to Integer frame numbers.
  342.      */
  343.     Hashtable parseSounds(String attr, Vector images)
  344.     throws MalformedURLException {
  345.     Hashtable result = new Hashtable();
  346.  
  347.     int imageNum = 0;
  348.     int numImages = images.size();
  349.     for (int i = 0; i < attr.length(); ) {
  350.         if (imageNum >= numImages) break;
  351.         
  352.         int next = attr.indexOf('|', i);
  353.         if (next == -1) next = attr.length();
  354.         
  355.         String sound = attr.substring(i, next);
  356.         if (sound.length() != 0) {
  357.         result.put(new Integer(imageNum),
  358.                new URL(soundSource, sound));
  359.         }
  360.         i = next + 1;
  361.         imageNum++;
  362.     }
  363.  
  364.     return result;
  365.     }
  366.  
  367.     /**
  368.      * Fetch the sounds named in the argument.
  369.      * Is restartable.
  370.      *
  371.      * @return URL of the first bogus file we hit, null if OK.
  372.      */
  373.     URL fetchSounds(Hashtable sounds) {
  374.     for (Enumeration e = sounds.keys() ; e.hasMoreElements() ;) {
  375.         Integer num = (Integer)e.nextElement();
  376.         Object o = sounds.get(num);
  377.         if (o instanceof URL) {
  378.         URL file = (URL)o;
  379.         tellLoadingMsg(file, soundLabel);
  380.         try {
  381.             sounds.put(num, getAudioClip(file));
  382.         } catch (Exception ex) {
  383.             return file;
  384.         }
  385.         }
  386.     }
  387.     return null;
  388.     }
  389.  
  390.     /**
  391.      * Parse the PAUSES parameter.  It looks like
  392.      * 1000|500|||750, etc., where each item corresponds to a
  393.      * source image.  Empty items mean that the corresponding image
  394.      * has no special duration, and should use the global one.
  395.      *
  396.      * @return a Hashtable of Integer pauses keyed to Integer
  397.      * frame numbers.
  398.      */
  399.     Hashtable parseDurations(String attr, Vector images) {
  400.     Hashtable result = new Hashtable();
  401.  
  402.     int imageNum = 0;
  403.     int numImages = images.size();
  404.     for (int i = 0; i < attr.length(); ) {
  405.         if (imageNum >= numImages) break;
  406.         
  407.         int next = attr.indexOf('|', i);
  408.         if (next == -1) next = attr.length();
  409.  
  410.         if (i != next - 1) {
  411.         int duration = Integer.parseInt(attr.substring(i, next));
  412.         result.put(new Integer(imageNum), new Integer(duration));
  413.         } else {
  414.         result.put(new Integer(imageNum),
  415.                new Integer(globalPause));
  416.         }
  417.         i = next + 1;
  418.         imageNum++;
  419.     }
  420.  
  421.     return result;
  422.     }
  423.  
  424.     /**
  425.      * Parse a String of form xxx@yyy and return a Point.
  426.      */
  427.     Point parsePoint(String s) throws ParseException {
  428.     int atPos = s.indexOf('@');
  429.     if (atPos == -1) throw new ParseException("Illegal position: "+s);
  430.     return new Point(Integer.parseInt(s.substring(0, atPos)),
  431.              Integer.parseInt(s.substring(atPos + 1)));
  432.     }
  433.  
  434.  
  435.     /**
  436.      * Parse the POSITIONS parameter.  It looks like
  437.      * 10@30|11@31|||12@20, etc., where each item is an X@Y coordinate
  438.      * corresponding to a source image.  Empty items mean that the
  439.      * corresponding image has the same position as the preceding one.
  440.      *
  441.      * @return a Hashtable of Points keyed to Integer frame numbers.
  442.      */
  443.     Hashtable parsePositions(String param, Vector images)
  444.     throws ParseException {
  445.     Hashtable result = new Hashtable();
  446.  
  447.     int imageNum = 0;
  448.     int numImages = images.size();
  449.     for (int i = 0; i < param.length(); ) {
  450.         if (imageNum >= numImages) break;
  451.         
  452.         int next = param.indexOf('|', i);
  453.         if (next == -1) next = param.length();
  454.  
  455.         if (i != next) {
  456.         result.put(new Integer(imageNum),
  457.                parsePoint(param.substring(i, next)));
  458.         }
  459.         i = next + 1;
  460.         imageNum++;
  461.     }
  462.  
  463.     return result;
  464.     }
  465.     
  466.     /**
  467.      * Get the dimensions of an image.
  468.      * @return the image's dimensions.
  469.      */
  470.     Dimension getImageDimensions(Image im) {
  471.     return new Dimension(im.getWidth(null), im.getHeight(null));
  472.     }
  473.  
  474.     /**
  475.      * Substitute an integer some number of times in a string, subject to
  476.      * parameter strings embedded in the string.
  477.      * Parameter strings:
  478.      *   %N - substitute the integer as is, with no padding.
  479.      *   %<digit>, for example %5 - substitute the integer left-padded with
  480.      *        zeros to <digits> digits wide.
  481.      *   %% - substitute a '%' here.
  482.      * @param inStr the String to substitute within
  483.      * @param theInt the int to substitute.
  484.      */
  485.     String doSubst(String inStr, int theInt) {
  486.     String padStr = "0000000000";
  487.     int length = inStr.length();
  488.     StringBuffer result = new StringBuffer(length);
  489.     
  490.     for (int i = 0; i < length;) {
  491.         char ch = inStr.charAt(i);
  492.         if (ch == '%') {
  493.         i++;
  494.         if (i == length) {
  495.             result.append(ch);
  496.         } else {
  497.             ch = inStr.charAt(i);
  498.             if (ch == 'N') {
  499.             // just stick in the number, unmolested
  500.             result.append(theInt+"");
  501.             i++;
  502.             } else {
  503.             int pad;
  504.             if ((pad = Character.digit(ch, 10)) != -1) {
  505.                 // we've got a width value
  506.                 String numStr = theInt+"";
  507.                 String scr = padStr+numStr;
  508.                 result.append(scr.substring(scr.length() - pad));
  509.                 i++;
  510.             } else {
  511.                 result.append(ch);
  512.                 i++;
  513.             }
  514.             }
  515.         }
  516.         } else {
  517.         result.append(ch);
  518.         i++;
  519.         }
  520.     }
  521.     return result.toString();
  522.     }    
  523.  
  524.     /**
  525.      * Stuff a range of image names into a Vector.
  526.      * @return a Vector of image URLs.
  527.      */
  528.     Vector prepareImageRange(int startImage, int endImage, String pattern)
  529.     throws MalformedURLException {
  530.     Vector result = new Vector(Math.abs(endImage - startImage) + 1);
  531.     if (pattern == null) {
  532.         pattern = "T%N.gif";
  533.     }
  534.     if (startImage > endImage) {
  535.         for (int i = startImage; i >= endImage; i--) {
  536.         result.addElement(new URL(imageSource, doSubst(pattern, i)));
  537.         }
  538.     } else {
  539.         for (int i = startImage; i <= endImage; i++) {
  540.         result.addElement(new URL(imageSource, doSubst(pattern, i)));
  541.         }
  542.     }
  543.     return result;
  544.     }
  545.  
  546.     
  547.     /**
  548.      * Initialize the applet.  Get parameters.
  549.      */
  550.     public void init() {
  551.  
  552.     tracker = new MediaTracker(this);
  553.     
  554.     try {
  555.         String param = getParameter("IMAGESOURCE");    
  556.         imageSource = (param == null) ? getDocumentBase() : new URL(getDocumentBase(), param + "/");
  557.     
  558.         param = getParameter("PAUSE");
  559.         globalPause =
  560.         (param != null) ? Integer.parseInt(param) : defaultPause;
  561.  
  562.         param = getParameter("REPEAT");
  563.         repeat = (param == null) ? true : (param.equalsIgnoreCase("yes") ||
  564.                            param.equalsIgnoreCase("true"));
  565.  
  566.         int startImage = 1;
  567.         int endImage = 1;
  568.         param = getParameter("ENDIMAGE");
  569.         if (param != null) {
  570.         endImage = Integer.parseInt(param);
  571.         param = getParameter("STARTIMAGE");
  572.         if (param != null) {
  573.             startImage = Integer.parseInt(param);
  574.         }
  575.         param = getParameter("NAMEPATTERN");
  576.         images = prepareImageRange(startImage, endImage, param);
  577.         } else {
  578.         param = getParameter("STARTIMAGE");
  579.         if (param != null) {
  580.             startImage = Integer.parseInt(param);
  581.             param = getParameter("NAMEPATTERN");
  582.             images = prepareImageRange(startImage, endImage, param);
  583.         } else {
  584.             param = getParameter("IMAGES");
  585.             if (param == null) {
  586.             showStatus("No legal IMAGES, STARTIMAGE, or ENDIMAGE "+
  587.                    "specified.");
  588.             return;
  589.             } else {
  590.             images = parseImages(param);
  591.             }
  592.         }
  593.         }
  594.  
  595.         param = getParameter("BACKGROUND");
  596.         if (param != null) {
  597.         backgroundImageURL = new URL(imageSource, param);
  598.         }
  599.  
  600.         param = getParameter("STARTUP");
  601.         if (param != null) {
  602.         startUpImageURL = new URL(imageSource, param);
  603.         }
  604.  
  605.         param = getParameter("SOUNDSOURCE");
  606.         soundSource = (param == null) ? imageSource : new URL(getDocumentBase(), param + "/");
  607.     
  608.         param = getParameter("SOUNDS");
  609.         if (param != null) {
  610.         sounds = parseSounds(param, images);
  611.         }
  612.  
  613.         param = getParameter("PAUSES");
  614.         if (param != null) {
  615.         durations = parseDurations(param, images);
  616.         }
  617.  
  618.         param = getParameter("POSITIONS");
  619.         if (param != null) {
  620.         positions = parsePositions(param, images);
  621.         }
  622.  
  623.         param = getParameter("SOUNDTRACK");
  624.         if (param != null) {
  625.         soundtrackURL = new URL(soundSource, param);
  626.         }
  627.     } catch (MalformedURLException e) {
  628.         showParseError(e);
  629.     } catch (ParseException e) {
  630.         showParseError(e);
  631.     }
  632.     
  633.  
  634.  
  635.     setFrameNum(0);
  636.     }
  637.  
  638.     void tellLoadingMsg(String file, String fileType) {
  639.     showStatus("Animator: loading "+fileType+" "+file);
  640.     }
  641.  
  642.     void tellLoadingMsg(URL url, String fileType) {
  643.     tellLoadingMsg(url.toExternalForm(), fileType);
  644.     }
  645.  
  646.     void clearLoadingMessage() {
  647.     showStatus("");
  648.     }
  649.     
  650.     void loadError(String fileName, String fileType) {
  651.     String errorMsg = "Animator: Couldn't load "+fileType+" "+
  652.         fileName;
  653.     showStatus(errorMsg);
  654.     System.err.println(errorMsg);
  655.     error = true;
  656.     repaint();
  657.     }
  658.  
  659.     void loadError(URL badURL, String fileType) {
  660.     loadError(badURL.toExternalForm(), fileType);
  661.     }
  662.  
  663.     void showParseError(Exception e) {
  664.     String errorMsg = "Animator: Parse error: "+e;
  665.     showStatus(errorMsg);
  666.     System.err.println(errorMsg);
  667.     error = true;
  668.     repaint();
  669.     }
  670.  
  671.     void startPlaying() {
  672.     if (soundtrack != null) {
  673.         soundtrack.loop();
  674.     }
  675.     }
  676.  
  677.     void stopPlaying() {
  678.     if (soundtrack != null) {
  679.         soundtrack.stop();
  680.     }
  681.     }
  682.  
  683.     /**
  684.      * Run the animation. This method is called by class Thread.
  685.      * @see java.lang.Thread
  686.      */
  687.     public void run() {
  688.     Thread me = Thread.currentThread();
  689.     URL badURL;
  690.     
  691.     me.setPriority(Thread.MIN_PRIORITY);
  692.  
  693.     if (! loaded) {
  694.         try {
  695.         // ... to do a bunch of loading.
  696.         if (startUpImageURL != null) {
  697.             tellLoadingMsg(startUpImageURL, imageLabel);
  698.             startUpImage = getImage(startUpImageURL);
  699.             tracker.addImage(startUpImage, STARTUP_ID);
  700.             tracker.waitForID(STARTUP_ID);
  701.             if (tracker.isErrorID(STARTUP_ID)) {
  702.             loadError(startUpImageURL, "start-up image");
  703.             }
  704.             Dimension size = getImageDimensions(startUpImage);
  705.             resize(size.width, size.height);
  706.             repaint();
  707.         }
  708.         
  709.         if (backgroundImageURL != null) {
  710.             tellLoadingMsg(backgroundImageURL, imageLabel);
  711.             backgroundImage = getImage(backgroundImageURL);
  712.             tracker.addImage(backgroundImage, BACKGROUND_ID);
  713.             tracker.waitForID(BACKGROUND_ID);
  714.             if (tracker.isErrorID(BACKGROUND_ID)) {
  715.             loadError(backgroundImageURL, "background image");
  716.             }
  717.             updateMaxDims(getImageDimensions(backgroundImage));
  718.             repaint();
  719.         }
  720.  
  721.         // Fetch the animation frames
  722.         if (!fetchImages(images)) {
  723.             // Need to add method to MediaTracker to return
  724.             // files that caused errors during loading.
  725.             loadError("an image", imageLabel);
  726.             return;
  727.         }
  728.  
  729.         if (soundtrackURL != null && soundtrack == null) {
  730.             tellLoadingMsg(soundtrackURL, imageLabel);
  731.             soundtrack = getAudioClip(soundtrackURL);
  732.             if (soundtrack == null) {
  733.             loadError(soundtrackURL, "soundtrack");
  734.             return;
  735.             }
  736.         }
  737.  
  738.         if (sounds != null) {
  739.             badURL = fetchSounds(sounds);
  740.             if (badURL != null) {
  741.             loadError(badURL, soundLabel);
  742.             return;
  743.             }
  744.         }
  745.  
  746.         clearLoadingMessage();
  747.  
  748.         offScrImage = createImage(maxWidth, maxHeight);
  749.         offScrGC = offScrImage.getGraphics();
  750.         offScrGC.setColor(Color.lightGray);
  751.  
  752.         resize(maxWidth, maxHeight);
  753.         loaded = true;
  754.         error = false;
  755.         } catch (Exception e) {
  756.         error = true;
  757.         e.printStackTrace();
  758.         }
  759.     }
  760.  
  761.     if (userPause) {
  762.         return;
  763.     }
  764.  
  765.     if (repeat || frameNum < images.size()) {
  766.         startPlaying();
  767.     }
  768.  
  769.     try {
  770.         if (images.size() > 1) {
  771.         while (maxWidth > 0 && maxHeight > 0 && engine == me) {
  772.             if (frameNum >= images.size()) {
  773.             if (!repeat) {
  774.                 return;
  775.             }
  776.             setFrameNum(0);
  777.             }
  778.             repaint();
  779.  
  780.             if (sounds != null) {
  781.             AudioClip clip =
  782.                 (AudioClip)sounds.get(frameNumKey);
  783.             if (clip != null) {
  784.                 clip.play();
  785.             }
  786.             }
  787.  
  788.             try {
  789.             Integer pause = null;
  790.             if (durations != null) {
  791.                 pause = (Integer)durations.get(frameNumKey);
  792.             }
  793.             if (pause == null) {
  794.                 Thread.sleep(globalPause);
  795.             } else {
  796.                 Thread.sleep(pause.intValue());
  797.             }
  798.             } catch (InterruptedException e) {
  799.             // Should we do anything?
  800.             }
  801.             setFrameNum(frameNum+1);
  802.         }
  803.         }
  804.     } finally {
  805.         stopPlaying();
  806.     }
  807.     }
  808.  
  809.     /**
  810.      * No need to clear anything; just paint.
  811.      */
  812.     public void update(Graphics g) {
  813.     paint(g);
  814.     }
  815.  
  816.     /**
  817.      * Paint the current frame.
  818.      */
  819.     public void paint(Graphics g) {
  820.     if (error || !loaded) {
  821.         if (startUpImage != null) {
  822.         if (tracker.checkID(STARTUP_ID)) {
  823.             g.drawImage(startUpImage, 0, 0, this);
  824.         }
  825.         } else {
  826.         if (backgroundImage != null) {
  827.             if (tracker.checkID(BACKGROUND_ID)) {
  828.             g.drawImage(backgroundImage, 0, 0, this);
  829.             }
  830.         } else {
  831.             g.clearRect(0, 0, maxWidth, maxHeight);
  832.         }
  833.         }
  834.     } else {
  835.         if ((images != null) && (images.size() > 0)) {
  836.         if (frameNum < images.size()) {
  837.             if (backgroundImage == null) {
  838.             offScrGC.fillRect(0, 0, maxWidth, maxHeight);
  839.             } else {
  840.             offScrGC.drawImage(backgroundImage, 0, 0, this);
  841.             }
  842.  
  843.             Image image = (Image)images.elementAt(frameNum);
  844.             Point pos = null;
  845.             if (positions != null) {
  846.             pos = (Point)positions.get(frameNumKey);
  847.             }
  848.             if (pos != null) {
  849.             xPos = pos.x;
  850.             yPos = pos.y;
  851.             }
  852.             offScrGC.drawImage(image, xPos, yPos, this);
  853.             g.drawImage(offScrImage, 0, 0, this);
  854.         } else {
  855.             // no more animation, but need to draw something
  856.             dbg("No more animation; drawing last image.");
  857.             if (backgroundImage == null) {
  858.             g.fillRect(0, 0, maxWidth, maxHeight);
  859.             } else {
  860.             g.drawImage(backgroundImage, 0, 0, this);
  861.             }
  862.             g.drawImage((Image)images.lastElement(), 0, 0, this);
  863.         }
  864.         }
  865.     }
  866.     }
  867.  
  868.     /**
  869.      * Start the applet by forking an animation thread.
  870.      */
  871.     public void start() {
  872.     if (engine == null) {
  873.         engine = new Thread(this);
  874.         engine.start();
  875.     }
  876.     }
  877.  
  878.     /**
  879.      * Stop the insanity, um, applet.
  880.      */
  881.     public void stop() {
  882.     if (engine != null && engine.isAlive()) {
  883.         engine.stop();
  884.     }
  885.     engine = null;
  886.     }
  887.  
  888.     /**
  889.      * Pause the thread when the user clicks the mouse in the applet.
  890.      * If the thread has stopped (as in a non-repeat performance),
  891.      * restart it.
  892.      */
  893.     public boolean handleEvent(Event evt) {
  894.     if (evt.id == Event.MOUSE_DOWN) {
  895.         if (loaded) {
  896.         if (engine != null && engine.isAlive()) {
  897.             if (userPause) {
  898.             engine.resume();
  899.             startPlaying();
  900.             } else {
  901.             engine.suspend();
  902.             stopPlaying();
  903.             }
  904.             userPause = !userPause;
  905.         } else {
  906.             userPause = false;
  907.             setFrameNum(0);
  908.             engine = new Thread(this);
  909.             engine.start();
  910.         }
  911.         }
  912.         return true;
  913.     } else {        
  914.         return super.handleEvent(evt);
  915.     }
  916.     }
  917.     
  918. }
  919.  
  920.  
  921. class ParseException extends Exception {
  922.     ParseException(String s) {
  923.     super(s);
  924.     }
  925. }
  926.  
  927. class ImageNotFoundException extends Exception {
  928.     ImageNotFoundException(ImageProducer source) {
  929.     super(source+"");
  930.     }
  931. }
  932.  
  933.